By: Allyson Lynch
import pandas as pd
import urllib.request
import urllib.error
import urllib.parse
from rdkit import Chem
import numpy as np
import statistics
Creates a file that cleans the experimental smiles names
def cactus(amine):
"""takes an amine name and calls a smiles string from cactus"""
compliant_name = urllib.parse.quote_plus(amine)
url = "https://cactus.nci.nih.gov/chemical/structure/"+amine+"/"+"smiles"
req = urllib.request.Request(url)
try: resp = urllib.request.urlopen(req)
except urllib.error.URLError as e:
resp = None
if resp=="" or resp==None:
resp = amine
else:
resp = resp.read().decode(resp.headers.get_content_charset() or 'utf-8')
if resp.find("!")!=-1: #cannot convert from rdkit smiles back to cactus smiles
resp = amine
return resp
def check_name_cactus(name):
"""takes an amine name and returns a neutralized and canonicalized smiles string"""
resp = cactus(name) #pull name from cactus
mol = Chem.MolFromSmiles(resp) #canonicalize
if mol!=None:
resp = Chem.MolToSmiles(mol, isomericSmiles=False)
final = cactus(resp) #re-run through cactus to clean
return final
def clean_salts(outcomes_file):
"""takes a file of outcomes and cleans the smiles string"""
smiles_df = pd.read_table(outcomes_file)#, usecols=[i for i in range(17)]) #read file
smiles_df['Smiles'] = smiles_df['_raw_SMILES.nosalt'].apply(check_name_cactus)
smiles_df.to_csv("Outputs/03 Outcomes/"+outcomes_file[18:-12]+"_clean.tsv", sep='\t') #save as tsv
Categorizes each amine by success and success rate
def outcome_success(outcomes_file):
"""takes a file of outcomes and returns a file of success and success rates per smiles"""
smiles_df = pd.read_table(outcomes_file) #read file
outcome_df = pd.DataFrame(smiles_df.groupby('Smiles')['_out_Outcome'].max()) #success number per smiles
clean_df = outcome_df.rename(columns={'_out_Outcome':'OUT_anySuccess?'}).replace({4:True, 1:False, 2:False, 3:False}) #clean
count_df = pd.DataFrame(smiles_df.groupby('Smiles')['Smiles'].count()).rename(columns={'Smiles':'count'}) #count occurances
count_4_df = smiles_df.loc[smiles_df['_out_Outcome']==4] #only keep 4s
count_4_df = pd.DataFrame(count_4_df.groupby('Smiles')['Smiles'].count()).rename(columns={'Smiles':'count_4'}) #count 4 occurances
count_df = pd.merge(count_df, count_4_df, left_index=True, right_index=True, how='outer').fillna(0)
clean_df['OUT_successRate'] = count_df['count_4']/count_df['count'] #calculate success rate
clean_df.to_csv("Outputs/03 Outcomes/"+outcomes_file[20:-10]+"_outcomestats.tsv", sep='\t') #save as tsv
Calculates the probability of popular and unpouplar amines success and failure
def bootstrap(l1, nrepeat=10000): #calculate error bars
sample = [sum(np.random.choice(l1, size=len(l1))) for i in range(nrepeat)] #sum of lists by random sample
r = [i/len(l1) for i in sample] #probability
return [statistics.mean(r), statistics.stdev(r)]
def shorten(stdinchikey):
inchikey = stdinchikey[:23]
return inchikey
def success_popularity(success_file, success_rate=False):
"""takes a file of outcome success data and popularity and returns a file that calculates a number success per amine, probability, and bootstrap error"""
success_df = pd.read_table(success_file) #read file
inchi_df = pd.read_table("Outputs/01 CCDC Smiles File/Input Files/standardize_names.tsv") #read file
success_df = pd.merge(success_df, inchi_df, left_on='Smiles', right_on='smiles')
success_df['inchikey'] = success_df['stdinchikey'].apply(shorten) #add inchi key column
popularity_df = pd.read_table("Outputs/02 Popularity Files/amine_popularity.tsv")
success_popularity = pd.merge(popularity_df, success_df, on='inchikey', how='right').fillna(-1)
final_df = pd.DataFrame({'popularity':['Popular', 'NotPopular', 'Unpopular', 'Absent']})
for oxide in ["metaloxides_popular", "metalborates_popular"]:
sp = success_popularity.loc[(success_popularity[oxide]==1) & (success_popularity['OUT_anySuccess?']==True)]
fp = success_popularity.loc[(success_popularity[oxide]==1) & (success_popularity['OUT_anySuccess?']==False)]
sn = success_popularity.loc[(success_popularity[oxide]<=0) & (success_popularity['OUT_anySuccess?']==True)]
fn = success_popularity.loc[(success_popularity[oxide]<=0) & (success_popularity['OUT_anySuccess?']==False)]
su = success_popularity.loc[(success_popularity[oxide]==0) & (success_popularity['OUT_anySuccess?']==True)]
fu = success_popularity.loc[(success_popularity[oxide]==0) & (success_popularity['OUT_anySuccess?']==False)]
sa = success_popularity.loc[(success_popularity[oxide]==-1) & (success_popularity['OUT_anySuccess?']==True)]
fa = success_popularity.loc[(success_popularity[oxide]==-1) & (success_popularity['OUT_anySuccess?']==False)]
popular = len(sp['OUT_successRate'].tolist() + fp['OUT_successRate'].tolist())
notpopular = len(sn['OUT_successRate'].tolist() + fn['OUT_successRate'].tolist())
unpopular = len(su['OUT_successRate'].tolist() + fu['OUT_successRate'].tolist())
absent = len(sa['OUT_successRate'].tolist() + fa['OUT_successRate'].tolist())
if success_rate==False:
success_popularity.loc[success_popularity['OUT_anySuccess?'] == True, 'OUT_anySuccess?'] = 1 #replace True/False with 1/0
success_popularity.loc[success_popularity['OUT_anySuccess?'] == False, 'OUT_anySuccess?'] = 0
populars = bootstrap(sp['OUT_successRate'].tolist() + fp['OUT_successRate'].tolist())[1]
notpopulars = bootstrap(sn['OUT_successRate'].tolist() + fn['OUT_successRate'].tolist())[1]
unpopulars = bootstrap(su['OUT_successRate'].tolist() + fu['OUT_successRate'].tolist())[1]
absents = bootstrap(sa['OUT_successRate'].tolist() + fa['OUT_successRate'].tolist())[1]
final_df['number_'+oxide[:-9]] = [len(sp.index.tolist()), len(fp.index.tolist()), len(sn.index.tolist()), len(fn.index.tolist()), len(su.index.tolist()), len(fu.index.tolist()), len(sa.index.tolist()), len(fa.index.tolist())]
final_df['probability_'+oxide[:-9]] = [len(sp.index.tolist())/popular, len(fp.index.tolist())/popular, len(sn.index.tolist())/notpopular, len(fn.index.tolist())/notpopular, len(su.index.tolist())/unpopular, len(fu.index.tolist())/unpopular, len(sa.index.tolist())/absent, len(fa.index.tolist())/absent]
final_df['error_'+oxide[:-9]] = [populars, populars, notpopulars, notpopulars, unpopulars, unpopulars, absents, absents]
final_df.to_csv("Outputs/03 Outcomes/success_popularity.tsv", sep='\t') #save as tsv
Calculates basic statistics of the outcomes
def stats(outcomes_file, include_absent_amines=False):
"""takes a popularity data file, outcomes data file, and optional parameter for including not popular (-1) and returns a table of descriptive statistics with columns for each molecule by popularity"""
df_popularity = pd.read_table("Outputs/02 Popularity Files/amine_popularity.tsv")
df_properties = pd.read_table(outcomes_file)
properties = ["mean", "median", "stdev"]
popularity_properties = pd.merge(df_popularity, df_properties, left_on='smiles', right_on='Smiles', how='right').fillna(-1) #merge popularity and properties by smiles
final_df = pd.DataFrame({'properties':properties}, index=properties) #initiliaze dataframe
#make a list per molecule...
for molecule in ["metalborates_popular"]:
#make popular and unpopular data frames
popular_df = popularity_properties.loc[popularity_properties[molecule]==1] #popular dataframe
popular = popular_df['_out_Outcome'].tolist()
if include_absent_amines:
unpopular_df = popularity_properties.loc[popularity_properties[molecule] <= 0] #unpopular + absent dataframe
else:
unpopular_df = popularity_properties.loc[popularity_properties[molecule]==0] #unpopular dataframe
unpopular = unpopular_df['_out_Outcome'].tolist()
#...of the stats for each property
means = [statistics.mean(popular), statistics.mean(unpopular)]
medians = [statistics.median(popular), statistics.median(unpopular)]
stdevs = [statistics.stdev(popular), statistics.stdev(unpopular)]
#create dataframe of results
ks_df = pd.DataFrame({molecule:[means, medians, stdevs]}, index=properties) #create a dataframe with molecule
final_df = pd.merge(ks_df, final_df, left_index=True, right_index=True) #merge new data frame with existing on properties
final_df.drop('properties', axis=1, inplace=True)
final_df.to_csv("Outputs/03 Outcomes/Statistics.tsv", sep='\t') #save as tsv
Categorizes (popular, absent, etc.) and counts the number of amines
def experiment_popularity(file="metaloxides"):
"""creates a file of count information on the experimental amines popularity"""
metalborates = pd.read_table("Outputs/02 Popularity Files/"+file+"_popularity.tsv")
absent = []
unpopulars = []
populars = []
totals = []
unique = []
for exp in ['triangle', 'human']:
experiment = pd.read_table("Outputs/03 Outcomes/"+exp+"_outcomestats.tsv")
merge_df = pd.merge(metalborates, experiment, left_on='smiles', right_on='Smiles', how='right').fillna(-1)
merge_df.to_csv(exp+"experimental_popularity.tsv", sep='\t') #save as tsv
count_df = pd.DataFrame(merge_df.groupby(file+'_popular')[file+'_popular'].count())
absent.append(count_df.iloc[0][file+'_popular'])
unpopulars.append(count_df.iloc[1][file+'_popular'])
populars.append(count_df.iloc[2][file+'_popular'])
unique.append(len(experiment['Smiles'].tolist()))
full = pd.read_table("Outputs/03 Outcomes/"+exp+"_clean.tsv")
totals.append(len(full['Smiles'].tolist()))
final_df = pd.DataFrame({'experiments_total':totals, 'amines_unique':unique, 'number_popular': populars, 'number_unpopular': unpopulars, 'number_absent': absent}, index=["triangle", "human"])
final_df.to_csv("Outputs/03 Outcomes/experimental_popularity_analysis_"+file+".tsv", sep='\t') #save as tsv
def outcomes(outcomes_file, success_rate=False, include_absent_amines=False):
for file in outcomes_file:
clean_salts(file)
outcome_success("Outputs/03 Outcomes/"+file[18:-12]+"_clean.tsv")
success_popularity("Outputs/03 Outcomes/"+outcomes_file[1][18:-12]+"_outcomestats.tsv", success_rate)
stats("Outputs/03 Outcomes/"+outcomes_file[1][18:-12]+"_clean.tsv", include_absent_amines) #update code for both human and triangle like experiment
experiment_popularity()
outcomes(["Data/Experimental/human_borates.txt", "Data/Experimental/triangle_borates.txt"])